package main

import (
	"../common"
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
	_ "net/http/pprof"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type handlerError struct {
	Error   error
	Message string
	Code    int
}

// a custom type that we can use for handling errors and formatting responses
type handler func(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError)

func (fn handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	response, err := fn(w, r)
	if err != nil {
		http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Message+": "+err.Error.Error()), err.Code)
		return
	}
	if response == nil {
		http.Error(w, "Internal server error. Check the logs.", http.StatusInternalServerError)
		return
	}
	switch response.(type) {
	case []byte:
		log.Info("text response")
		w.Header().Set("Content-Type", "text/plain; version=0.0.4")
		w.Write(response.([]byte))
	default:
		bytes, e := json.Marshal(response)
		if e != nil {
			http.Error(w, fmt.Sprintf("Error marshalling JSON:'%s'", e), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write(bytes)
	}
}

func showConns(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showConns")
	ip := mux.Vars(r)["ip"]
	if ip == "" {
		ip = ".*"
	}
	return counter.QueryConnsByIP(ip), nil
}

func showSender(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showSender")
	return counter.QuerySenderSummary(), nil
}

func showProme(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showProme")
	var buffer bytes.Buffer
	log.Infof("size of prome %d", len(cache.Snapshot()))
	for ip, m := range cache.Snapshot() {
		ipstr := strings.Replace(ip, ".", "_", -1)
		buffer.WriteString(fmt.Sprintf("# TYPE IP:%s gauge\n", ipstr))
		for metric, data := range m {
			for _, mh := range conf.PrometheusMetricHead {
				if strings.Contains(metric, mh) {
					sub := strings.Replace(metric, mh+".", "", -1)
					s := strings.Replace(sub, ".", "_", -1)
					m := strings.Replace(mh, ".", "_", -1)
					line := fmt.Sprintf("IP:%s{metric=\"%s\",sub=\"%s\"} %f %d000\n", ipstr, m, s, data.Val, data.Time)
					buffer.WriteString(line)
				}
			}
		}
		buffer.WriteString("\n")
	}
	return buffer.Bytes(), nil
}

func showConf(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showConf")
	return &conf, nil
}

func parseTime(s string, base uint32) (ret uint32) {
	if base == 0 {
		base = uint32(time.Now().Unix())
	}
	re := regexp.MustCompile(`^(\+|\-)(\d+[a-zA-Z]+)+`)
	subre := regexp.MustCompile(`(?P<int>\d+)(?P<unit>[a-zA-Z]+)`)
	if ok := re.MatchString(s); ok {
		for _, ss := range subre.FindAllString(s, -1) {
			match := subre.FindStringSubmatch(ss)
			if len(match) == 3 {
				count, err := strconv.Atoi(match[1])
				if err != nil {
					continue
				}
				switch match[2] {
				case "min", "minute":
					ret += uint32(count * 60)
				case "second", "sec":
					ret += uint32(count)
				case "day":
					ret += uint32(count * 60 * 24)
				default:
					continue
				}
			}
		}
		if len(s) > 0 && s[0] == '-' {
			return base - ret
		}
		return base + ret
	}
	return base
}

func parse(r *http.Request) (from, to uint32) {
	base := uint32(time.Now().Unix())
	to = base
	if err := r.ParseForm(); err != nil {
		return
	}
	if str, ok := r.Form["from"]; ok {
		from = parseTime(str[0], base)
	}
	if from == 0 {
		from = base - (24 * 2600 * 7)
	}
	if str, ok := r.Form["to"]; ok {
		to = parseTime(str[0], base)
	}
	return
}

//cache.QueryMetricNames(ip, p)
//cache.QueryByIP(ip)
//cache.QueryRange(ip, metric, from, to)

func showAllMetricNames(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showMetricNames")
	err := r.ParseForm()
	if err != nil {
		return nil, &handlerError{nil, err.Error(), 404}
	}

	ip := mux.Vars(r)["ip"]
	if patterns, ok := r.Form["pattern"]; ok {
		ret, _ := cache.QueryMetricNames(ip, patterns)
		log.Infof("query ip: %s , pattern: %v, result size: %d", ip, patterns, len(ret))
		return ret, nil
	}
	if m, ts := cache.QueryByIP(ip); m != "" {
		log.Infof("query ip: %s , newest metric: %s, last update time: %d", ip, m, ts)
		return fmt.Sprintf("{\"%s\":%d}", m, ts), nil
	} else {
		return m, nil
	}
	return nil, nil
}

func showAllMetricValues(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showMetricValue")
	metric := mux.Vars(r)["metric"]
	ip := mux.Vars(r)["ip"]
	from, to := parse(r)
	ret, _ := cache.QueryRange(ip, metric, from, to)
	log.Infof("query ip: %s , metric: %s, from  %d, to  %d , result size: %d", ip, metric, from, to, len(ret))
	return ret, nil
}

func showStatics(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) {
	log.Info("request for showStatics")
	ip := mux.Vars(r)["ip"]
	if ip == "" {
		ip = ".*"
	}
	return counter.QueryStaticsByIP(ip), nil
}

var (
	counter *Counter
	conf    *Config
	cache   *common.CacheManager
)

func HttpListener(addr string, c *Counter, ca *common.CacheManager, cfg *Config) {
	counter = c
	cache = ca
	conf = cfg
	router := mux.NewRouter()
	router.Handle("/conns/", handler(showConns)).Methods("GET")
	router.Handle("/conns/{ip}", handler(showConns)).Methods("GET")
	router.Handle("/conf/", handler(showConf)).Methods("GET")
	router.Handle("/messages/", handler(showStatics)).Methods("GET")
	router.Handle("/messages/{ip}", handler(showStatics)).Methods("GET")
	router.Handle("/sender/", handler(showSender)).Methods("GET")
	router.Handle("/prome/", handler(showProme)).Methods("GET")
	router.Handle("/datapoint/{ip}", handler(showAllMetricNames)).Methods("GET")
	router.Handle("/datapoint/{ip}/{metric}", handler(showAllMetricValues)).Methods("GET")

	http.Handle("/", router)

	var err error
	addrs := strings.Split(addr, ":")
	if len(addrs) != 2 {
		log.Errorf("http address error %s", addr)
		os.Exit(1)
	}

	addr = addrs[0]
	port, err := strconv.Atoi(addrs[1])
	if err != nil {
		log.Errorf("http address error %s , %v", addr, err)
		os.Exit(1)
	}
	if len(cfg.EtcdAddr) > 0 {
		//go KeepAliveAndFindPeers(port, cfg)
	}
	err = http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), nil)
	if err != nil {
		log.Errorf("start http server error %v", err)
		os.Exit(1)
	}
}
